#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
天翼云盘PC版文件操作类
包含上传、下载、文件管理等方法
"""

import os
import io
import math
import time
import json
import base64
import hashlib
from typing import Dict, List, Optional, Any, Union, BinaryIO
from urllib.parse import quote
from concurrent.futures import ThreadPoolExecutor, as_completed

from .base_client import API_URL, UPLOAD_URL

class FileOperations:
    """文件操作类"""
    
    def get_files(self, folder_id: str = "-11", is_family: bool = False, 
                 page_num: int = 1, page_size: int = 1000) -> Optional[Dict[str, Any]]:
        """获取文件列表
        
        返回完整的API响应数据，包含以下结构：
        {
            "res_code": 0,  # 响应状态码，0表示成功
            "res_message": "成功",  # 响应消息
            "fileListAO": {
                "count": 17,  # 总项目数量
                "fileList": [...],  # 文件列表
                "fileListSize": 10,  # 文件数量
                "folderList": [...]  # 文件夹列表
            },
            "lastRev": 20250726222758  # 最后版本号
        }
        
        Args:
            folder_id: 文件夹ID，默认为根目录(-11)
            is_family: 是否为家庭云模式
            page_num: 页码，从1开始
            page_size: 每页大小，默认1000
            
        Returns:
            完整的API响应数据字典，失败时返回None
        """
        if not self.is_login():
            print("未登录，无法获取文件列表")
            return None
        
        if is_family:
            url = f"{API_URL}/family/file/listFiles.action"
            if not self.family_id:
                self.family_id = self.get_family_id()
        else:
            url = f"{API_URL}/listFiles.action"
        
        headers = self.signature_header(url, "GET", "", is_family)
        params = self.client_suffix()
        
        # 基础参数
        params.update({
            'folderId': folder_id,
            'fileType': '0',
            'mediaAttr': '0', 
            'iconOption': '5',
            'pageNum': str(page_num),
            'pageSize': str(page_size)
        })
        
        if is_family:
            params.update({
                'familyId': self.family_id,
                'orderBy': self._to_family_order_by('filename'),
                'descending': self._to_desc('asc')
            })
        else:
            params.update({
                'recursive': '0',
                'orderBy': 'filename',
                'descending': self._to_desc('asc')
            })
        
        if self.enable_debug:
            self.logger.debug(f"获取文件列表 URL: {url}")
            self.logger.debug(f"获取文件列表 参数: {params}")
            self.logger.debug(f"获取文件列表 请求头: {headers}")
        
        try:
            response = self.session.get(url, headers=headers, params=params)
            if self.enable_debug:
                self.logger.debug(f"获取文件列表响应状态码: {response.status_code}")
            
            if response.status_code == 200:
                return response.json()
        except Exception as e:
            print(f"获取文件列表失败: {e}")
            if self.enable_debug:
                self.logger.error(f"获取文件列表异常: {e}")
        return None
    
    def _to_family_order_by(self, order_by: str) -> str:
        """转换家庭云排序字段"""
        mapping = {
            'filename': '1',
            'filesize': '2', 
            'lastOpTime': '3'
        }
        return mapping.get(order_by, '1')
    
    def _to_desc(self, order_direction: str) -> str:
        """转换排序方向"""
        if order_direction.lower() == 'desc':
            return 'true'
        return 'false'
    
    def get_download_url(self, file_id: str, is_family: bool = False) -> Optional[str]:
        """获取下载链接"""
        if not self.is_login():
            print("未登录，无法获取下载链接")
            return None
        
        if is_family:
            url = f"{API_URL}/family/file/getFileDownloadUrl.action"
        else:
            url = f"{API_URL}/getFileDownloadUrl.action"
        
        headers = self.signature_header(url, "GET", "", is_family)
        params = self.client_suffix()
        params['fileId'] = file_id
        
        if is_family:
            params['familyId'] = self.family_id
        else:
            params.update({
                'dt': '3',
                'flag': '1'
            })
        
        if self.enable_debug:
            self.logger.debug(f"获取下载链接 URL: {url}")
            self.logger.debug(f"获取下载链接 参数: {params}")
            self.logger.debug(f"获取下载链接 请求头: {headers}")
        
        try:
            response = self.session.get(url, headers=headers, params=params)
            if self.enable_debug:
                self.logger.debug(f"获取下载链接响应状态码: {response.status_code}")
                self.logger.debug(f"获取下载链接响应内容: {response.text}")
            
            if response.status_code == 200:
                result = response.json()
                download_url = result.get('fileDownloadUrl')
                if download_url:
                    # 处理URL格式
                    download_url = download_url.replace('&amp;', '&').replace('http://', 'https://')
                    
                    # 处理重定向获取真实下载链接
                    try:
                        redirect_response = self.session.get(download_url, allow_redirects=False)
                        if redirect_response.status_code == 302:
                            real_download_url = redirect_response.headers.get('location')
                            if real_download_url:
                                download_url = real_download_url
                    except Exception:
                        pass
                    
                    return download_url
        except Exception as e:
            print(f"获取下载链接失败: {e}")
        return None
    
    def download_file(self, file_id: str, save_path: str, is_family: bool = False, filename: str = None) -> bool:
        """下载文件"""
        download_url = self.get_download_url(file_id, is_family)
        if not download_url:
            print("无法获取下载链接")
            return False
        
        try:
            # 如果save_path是目录，需要添加文件名
            if os.path.isdir(save_path) or save_path.endswith('\\') or save_path.endswith('/'):
                if not filename:
                    filename = f"file_{file_id}"
                save_path = os.path.join(save_path, filename)
            
            # 如果文件已存在，自动重命名
            if os.path.exists(save_path):
                base_name, ext = os.path.splitext(save_path)
                counter = 1
                while os.path.exists(f"{base_name}({counter}){ext}"):
                    counter += 1
                save_path = f"{base_name}({counter}){ext}"
                print(f"文件已存在，自动重命名为: {os.path.basename(save_path)}")
            
            # 确保目录存在
            parent_dir = os.path.dirname(save_path)
            if parent_dir:
                os.makedirs(parent_dir, exist_ok=True)
            
            print(f"开始下载文件到: {save_path}")
            response = self.session.get(download_url, stream=True)
            
            if response.status_code == 200:
                with open(save_path, 'wb') as f:
                    for chunk in response.iter_content(chunk_size=8192):
                        if chunk:
                            f.write(chunk)
                
                print(f"文件下载完成: {save_path}")
                return True
            else:
                print(f"下载失败，状态码: {response.status_code}")
                return False
                
        except Exception as e:
            print(f"下载文件失败: {e}")
            return False
    
    def make_dir(self, parent_id: str, dir_name: str, is_family: bool = False) -> Optional[str]:
        """创建文件夹"""
        if not self.is_login():
            print("未登录，无法创建文件夹")
            return None
        
        if is_family:
            url = f"{API_URL}/family/file/createFolder.action"
        else:
            url = f"{API_URL}/createFolder.action"
        
        headers = self.signature_header(url, "POST", "", is_family)
        data = self.client_suffix()
        data.update({
            'parentFolderId': parent_id,
            'folderName': dir_name
        })
        
        if is_family:
            data['familyId'] = self.family_id
        
        if self.enable_debug:
            self.logger.debug(f"创建文件夹 URL: {url}")
            self.logger.debug(f"创建文件夹 数据: {data}")
            self.logger.debug(f"创建文件夹 请求头: {headers}")
        
        try:
            response = self.session.post(url, headers=headers, data=data)
            if self.enable_debug:
                self.logger.debug(f"创建文件夹响应状态码: {response.status_code}")
                self.logger.debug(f"创建文件夹响应内容: {response.text}")
            
            if response.status_code == 200:
                result = response.json()
                if result.get('res_code') == 0:
                    folder_id = result.get('id')
                    print(f"文件夹创建成功: {dir_name} (ID: {folder_id})")
                    return folder_id
                else:
                    print(f"创建文件夹失败: {result.get('res_message')}")
        except Exception as e:
            print(f"创建文件夹失败: {e}")
        return None
    
    def calculate_slice_size(self, file_size: int) -> int:
        """计算分片大小"""
        DEFAULT = 1024 * 1024 * 10  # 10MIB
        
        if file_size > DEFAULT * 2 * 999:
            # 超过20GB的文件，使用更大的分片
            return int(max(math.ceil((file_size / 1999) / DEFAULT), 5) * DEFAULT)
        elif file_size > DEFAULT * 999:
            # 超过10GB的文件，使用20MB分片
            return DEFAULT * 2  # 20MIB
        else:
            # 默认使用10MB分片
            return DEFAULT
    
    def calculate_file_md5(self, file_data: Union[str, bytes, BinaryIO]) -> str:
        """计算文件MD5
        
        Args:
            file_data: 文件路径字符串、字节数据或文件对象
            
        Returns:
            MD5哈希值（大写）
        """
        hash_md5 = hashlib.md5()
        
        if isinstance(file_data, str):
            # 文件路径
            with open(file_data, "rb") as f:
                for chunk in iter(lambda: f.read(4096), b""):
                    hash_md5.update(chunk)
        elif isinstance(file_data, bytes):
            # 字节数据
            hash_md5.update(file_data)
        elif hasattr(file_data, 'read'):
            # 文件对象
            current_pos = file_data.tell() if hasattr(file_data, 'tell') else 0
            if hasattr(file_data, 'seek'):
                file_data.seek(0)
            
            for chunk in iter(lambda: file_data.read(4096), b""):
                hash_md5.update(chunk)
            
            # 恢复原始位置
            if hasattr(file_data, 'seek'):
                file_data.seek(current_pos)
        else:
            raise ValueError("不支持的文件数据类型")
        
        return hash_md5.hexdigest().lower()
    
    def calculate_slice_md5(self, file_data: Union[str, bytes, BinaryIO], offset: int, size: int) -> str:
        """计算分片MD5
        
        Args:
            file_data: 文件路径字符串、字节数据或文件对象
            offset: 偏移量
            size: 分片大小
            
        Returns:
            MD5哈希值（大写）
        """
        hash_md5 = hashlib.md5()
        
        if isinstance(file_data, str):
            # 文件路径
            with open(file_data, "rb") as f:
                f.seek(offset)
                remaining = size
                while remaining > 0:
                    chunk_size = min(4096, remaining)
                    chunk = f.read(chunk_size)
                    if not chunk:
                        break
                    hash_md5.update(chunk)
                    remaining -= len(chunk)
        elif isinstance(file_data, bytes):
            # 字节数据
            chunk = file_data[offset:offset + size]
            hash_md5.update(chunk)
        elif hasattr(file_data, 'read'):
            # 文件对象
            current_pos = file_data.tell() if hasattr(file_data, 'tell') else 0
            if hasattr(file_data, 'seek'):
                file_data.seek(offset)
            
            remaining = size
            while remaining > 0:
                chunk_size = min(4096, remaining)
                chunk = file_data.read(chunk_size)
                if not chunk:
                    break
                hash_md5.update(chunk)
                remaining -= len(chunk)
            
            # 恢复原始位置
            if hasattr(file_data, 'seek'):
                file_data.seek(current_pos)
        else:
            raise ValueError("不支持的文件数据类型")
        
        return hash_md5.hexdigest().upper()
    
    def check_fast_upload(self, filename: str, file_size: int, file_md5: str, 
                         parent_id: str = "-11", is_family: bool = False) -> Optional[Dict[str, Any]]:
        """检查文件是否支持秒传
        
        Args:
            filename: 文件名
            file_size: 文件大小
            file_md5: 文件MD5
            parent_id: 父目录ID
            is_family: 是否为家庭云
            
        Returns:
            如果支持秒传返回文件信息，否则返回None
        """
        if not self.is_login():
            print("未登录，无法检查秒传")
            return None
        
        slice_size = self.calculate_slice_size(file_size)
        count = math.ceil(file_size / slice_size)
        
        # 计算sliceMd5（如果只有一个分片则使用文件MD5）
        if count == 1:
            slice_md5 = file_md5
        else:
            # 多个分片时，这里只能使用文件MD5作为近似值
            # 实际应用中需要提供完整的分片MD5列表
            slice_md5 = file_md5
        
        # 初始化上传检查
        upload_info = self._init_upload(parent_id, filename, file_size, file_md5, slice_size, slice_md5, is_family)
        if not upload_info:
            return None
        
        # 检查文件是否已存在（支持秒传）
        if upload_info.get('fileDataExists') == 1:
            print(f"文件支持秒传: {filename}")
            # 直接提交秒传
            result = self._commit_upload(upload_info['uploadFileId'], None, None, is_family, True)
            if result:
                print(f"秒传成功: {filename}")
                return result
            else:
                print("秒传提交失败")
                return None
        
        print(f"文件不支持秒传: {filename}")
        return None
    
    def upload_file_stream(self, file_data: Union[str, bytes, BinaryIO], filename: str, 
                          parent_id: str = "-11", is_family: bool = False, 
                          overwrite: bool = True, try_fast_upload: bool = True) -> Optional[Dict[str, Any]]:
        """上传文件（支持字节流）
        
        Args:
            file_data: 文件数据（文件路径、字节数据或文件对象）
            filename: 文件名
            parent_id: 父目录ID，默认为根目录
            is_family: 是否为家庭云
            overwrite: 是否覆盖同名文件
            try_fast_upload: 是否尝试秒传
        
        Returns:
            上传成功返回文件信息，失败返回None
        """
        if not self.is_login():
            print("未登录，无法上传文件")
            return None
        
        # 获取文件大小
        if isinstance(file_data, str):
            if not os.path.exists(file_data):
                print(f"文件不存在: {file_data}")
                return None
            file_size = os.path.getsize(file_data)
        elif isinstance(file_data, bytes):
            file_size = len(file_data)
        elif hasattr(file_data, 'seek') and hasattr(file_data, 'tell'):
            # 文件对象
            current_pos = file_data.tell()
            file_data.seek(0, 2)  # 移动到文件末尾
            file_size = file_data.tell()
            file_data.seek(current_pos)  # 恢复原始位置
        else:
            raise ValueError("不支持的文件数据类型")
        
        print(f"开始上传文件: {filename} ({file_size:,} bytes)")
        
        if try_fast_upload:
            # 尝试秒传
            file_md5 = self.calculate_file_md5(file_data)
            result = self.check_fast_upload(filename, file_size, file_md5, parent_id, is_family)
            if result:
                return result
            print("秒传失败，使用普通上传")
        
        # 普通上传
        return self._stream_upload(file_data, filename, file_size, parent_id, is_family, overwrite)
    
    def _init_upload(self, parent_id: str, filename: str, file_size: int, 
                    file_md5: str = None, slice_size: int = None, slice_md5: str = None, 
                    is_family: bool = False) -> Optional[Dict[str, Any]]:
        """初始化上传"""
        if not self.is_login():
            print("未登录，无法初始化上传")
            return None
        
        if is_family:
            url = f"{UPLOAD_URL}/family/initMultiUpload"
        else:
            url = f"{UPLOAD_URL}/person/initMultiUpload"
        
        # 构建业务参数（需要加密）
        business_params = {
            'parentFolderId': parent_id,
            'fileName': quote(filename),
            'fileSize': str(file_size)
        }
        
        if file_md5:
            business_params['fileMd5'] = file_md5
        if slice_size:
            business_params['sliceSize'] = str(slice_size)
        if slice_md5:
            business_params['sliceMd5'] = slice_md5
        if is_family:
            business_params['familyId'] = self.family_id
        
        # 加密业务参数
        encrypted_params = self.encrypt_params(business_params, is_family)
        
        # 构建最终请求参数
        final_params = self.client_suffix()
        if encrypted_params:
            final_params['params'] = encrypted_params
        
        # 生成签名头
        headers = self.signature_header(url, "GET", encrypted_params, is_family)
        
        if self.enable_debug:
            self.logger.debug(f"初始化上传 URL: {url}")
            self.logger.debug(f"初始化上传 业务参数: {business_params}")
            self.logger.debug(f"初始化上传 加密参数: {encrypted_params[:50]}..." if encrypted_params else "无加密参数")
        
        try:
            response = self.session.get(url, headers=headers, params=final_params)
            if self.enable_debug:
                self.logger.debug(f"初始化上传响应状态码: {response.status_code}")
                self.logger.debug(f"初始化上传响应内容: {response.text}")
            
            if response.status_code == 200:
                result = response.json()
                if result.get('code') == 'SUCCESS' or result.get('res_code') == 0:
                    return result.get('data')
                else:
                    error_msg = result.get('msg') or result.get('res_message') or '未知错误'
                    print(f"初始化上传失败: {error_msg}")
        except Exception as e:
            print(f"初始化上传失败: {e}")
            if self.enable_debug:
                self.logger.error(f"初始化上传异常: {e}")
        return None
    
    def _get_upload_urls(self, upload_file_id: str, part_info: str, is_family: bool = False) -> Optional[List[Dict[str, Any]]]:
        """获取上传链接"""
        if is_family:
            url = f"{UPLOAD_URL}/family/getMultiUploadUrls"
        else:
            url = f"{UPLOAD_URL}/person/getMultiUploadUrls"
        
        # 构建业务参数
        business_params = {
            'uploadFileId': upload_file_id,
            'partInfo': part_info
        }
        
        # 加密业务参数
        encrypted_params = self.encrypt_params(business_params, is_family)
        
        # 构建最终请求参数
        final_params = self.client_suffix()
        if encrypted_params:
            final_params['params'] = encrypted_params
        
        # 生成签名头
        headers = self.signature_header(url, "GET", encrypted_params, is_family)
        
        if self.enable_debug:
            self.logger.debug(f"获取上传链接 URL: {url}")
            self.logger.debug(f"获取上传链接 业务参数: {business_params}")
        
        try:
            response = self.session.get(url, headers=headers, params=final_params)
            if self.enable_debug:
                self.logger.debug(f"获取上传链接响应状态码: {response.status_code}")
                self.logger.debug(f"获取上传链接响应内容: {response.text}")
            
            if response.status_code == 200:
                result = response.json()
                if result.get('code') == 'SUCCESS' or result.get('res_code') == 0:
                    upload_urls_data = result.get('uploadUrls') or result.get('data')
                    if upload_urls_data:
                        upload_urls = []
                        for part_key, url_info in upload_urls_data.items():
                            upload_urls.append({
                                'partNumber': int(part_key.replace('partNumber_', '')),
                                'requestURL': url_info['requestURL'],
                                'headers': self._parse_upload_headers(url_info.get('requestHeader', ''))
                            })
                        return upload_urls
                    return []
                else:
                    error_msg = result.get('msg') or result.get('res_message') or '未知错误'
                    print(f"获取上传链接失败: {error_msg}")
        except Exception as e:
            print(f"获取上传链接失败: {e}")
            if self.enable_debug:
                self.logger.error(f"获取上传链接异常: {e}")
        return None
    
    def _parse_upload_headers(self, header_str: str) -> Dict[str, str]:
        """解析上传请求头字符串"""
        headers = {}
        if not header_str:
            return headers
        
        for pair in header_str.split('&'):
            if '=' in pair:
                key, value = pair.split('=', 1)
                headers[key] = value
        return headers
    
    def _upload_part(self, upload_url: str, headers: Dict[str, str], 
                    file_data: Union[str, bytes, BinaryIO], offset: int, size: int) -> bool:
        """上传分片"""
        try:
            # 读取分片数据
            if isinstance(file_data, str):
                with open(file_data, 'rb') as f:
                    f.seek(offset)
                    data = f.read(size)
            elif isinstance(file_data, bytes):
                data = file_data[offset:offset + size]
            elif hasattr(file_data, 'read'):
                current_pos = file_data.tell() if hasattr(file_data, 'tell') else 0
                if hasattr(file_data, 'seek'):
                    file_data.seek(offset)
                data = file_data.read(size)
                if hasattr(file_data, 'seek'):
                    file_data.seek(current_pos)
            else:
                raise ValueError("不支持的文件数据类型")
            
            if self.enable_debug:
                self.logger.debug(f"上传分片 URL: {upload_url}")
                self.logger.debug(f"上传分片 大小: {len(data)} bytes")
            
            response = self.session.put(upload_url, headers=headers, data=data)
            
            if self.enable_debug:
                self.logger.debug(f"上传分片响应状态码: {response.status_code}")
            
            return response.status_code == 200
        except Exception as e:
            print(f"上传分片失败: {e}")
            if self.enable_debug:
                self.logger.error(f"上传分片异常: {e}")
            return False
    
    def _commit_upload(self, upload_file_id: str, file_md5: str = None, slice_md5: str = None, 
                      is_family: bool = False, overwrite: bool = True) -> Optional[Dict[str, Any]]:
        """提交上传"""
        if is_family:
            url = f"{UPLOAD_URL}/family/commitMultiUploadFile"
        else:
            url = f"{UPLOAD_URL}/person/commitMultiUploadFile"
        
        # 构建业务参数
        business_params = {
            'uploadFileId': upload_file_id,
            'isLog': '0',
            'opertype': '3' if overwrite else '1'
        }
        
        if file_md5:
            business_params['fileMd5'] = file_md5
        if slice_md5:
            business_params['sliceMd5'] = slice_md5
            business_params['lazyCheck'] = '1'
        
        # 加密业务参数
        encrypted_params = self.encrypt_params(business_params, is_family)
        
        # 构建最终请求参数
        final_params = self.client_suffix()
        if encrypted_params:
            final_params['params'] = encrypted_params
        
        # 生成签名头
        headers = self.signature_header(url, "GET", encrypted_params, is_family)
        
        if self.enable_debug:
            self.logger.debug(f"提交上传 URL: {url}")
            self.logger.debug(f"提交上传 业务参数: {business_params}")
        
        try:
            response = self.session.get(url, headers=headers, params=final_params)
            if self.enable_debug:
                self.logger.debug(f"提交上传响应状态码: {response.status_code}")
                self.logger.debug(f"提交上传响应内容: {response.text}")
            
            if response.status_code == 200:
                result = response.json()
                if result.get('code') == 'SUCCESS' or result.get('res_code') == 0:
                    return result.get('file') or result.get('data') or result
                else:
                    error_msg = result.get('msg') or result.get('res_message') or '未知错误'
                    print(f"提交上传失败: {error_msg}")
        except Exception as e:
            print(f"提交上传失败: {e}")
            if self.enable_debug:
                self.logger.error(f"提交上传异常: {e}")
        return None
    
    def _stream_upload(self, file_data: Union[str, bytes, BinaryIO], filename: str, file_size: int,
                      parent_id: str = "-11", is_family: bool = False, overwrite: bool = True) -> Optional[Dict[str, Any]]:
        """普通上传"""
        slice_size = self.calculate_slice_size(file_size)
        
        # 初始化上传（不提供MD5信息）
        upload_info = self._init_upload(parent_id, filename, file_size, None, slice_size, None, is_family)
        if not upload_info:
            return None
        
        count = math.ceil(file_size / slice_size)
        print(f"开始普通上传: {filename} ({count} 个分片)")
        
        # 计算文件MD5
        file_md5 = self.calculate_file_md5(file_data)
        slice_md5_list = []
        
        # 逐个上传分片
        for i in range(count):
            offset = i * slice_size
            current_size = min(slice_size, file_size - offset)
            
            # 计算当前分片MD5
            slice_md5 = self.calculate_slice_md5(file_data, offset, current_size)
            slice_md5_list.append(slice_md5)
            md5_bytes = bytes.fromhex(slice_md5)
            part_info = f"{i+1}-{base64.b64encode(md5_bytes).decode()}"
            
            # 获取上传链接
            upload_urls = self._get_upload_urls(upload_info['uploadFileId'], part_info, is_family)
            if not upload_urls or len(upload_urls) == 0:
                print(f"获取分片 {i+1} 上传链接失败")
                return None
            
            upload_url_info = upload_urls[0]
            upload_url = upload_url_info['requestURL']
            upload_headers = upload_url_info.get('headers', {})
            
            # 上传分片
            if self._upload_part(upload_url, upload_headers, file_data, offset, current_size):
                print(f"分片 {i+1}/{count} 上传成功")
            else:
                print(f"分片 {i+1}/{count} 上传失败")
                return None
        
        # 计算sliceMd5
        if file_size > slice_size:
            slice_md5_str = "\n".join(slice_md5_list)
            slice_md5 = hashlib.md5(slice_md5_str.encode()).hexdigest().upper()
        else:
            slice_md5 = file_md5
        
        # 提交上传
        print("所有分片上传完成，正在提交...")
        result = self._commit_upload(upload_info['uploadFileId'], file_md5, slice_md5, is_family, overwrite)
        if result:
            print(f"文件上传成功: {filename}")
            return result
        else:
            print("上传提交失败")
            return None